// Create the top-level elix.js and elix.d.ts files.

import * as fs from "fs/promises";
import path from "path";

const srcJsHeader = `/*
 * The complete set of Elix components and mixins.
 * 
 * This file is the primary entry point to the Elix package, so its exports are
 * what is obtained if you write \`import * from "elix"\`. However, in
 * production use it will be much more efficient to directly load just the
 * components you need from the /src folder. This file is also used during
 * testing, as it causes all Elix's elements to be loaded.
 * 
 * NOTE: Do not edit this file by hand. This file is generated during
 * publishing, or you can regenerate it with \`npm run prepublishOnly\`.
 */`;

const defineJsHeader = `/*
* The complete set of Elix components and mixins.
* 
* You can load this file as a convenience to auto-define all Elix components
* for immediate use. However, in production use it will be much more efficient
* to directly load just the components you need from the /define folder.
* 
* NOTE: Do not edit this file by hand. This file is generated during
* publishing, or you can regenerate it with \`npm run prepublishOnly\`.
*/`;

const tsHeader = `// TypeScript declarations for the complete Elix library.`;

async function createLibraryFile(destination, header, sourceFiles) {
  const destinationFolder = path.dirname(destination);

  const componentsAndMixins = [
    ...sourceFiles.components,
    ...sourceFiles.mixins,
  ];
  const sorted = sortByBaseName(componentsAndMixins);
  const defaultExports = sorted
    .map((file) => {
      let relativePath = path.relative(destinationFolder, file);
      if (path.sep === "\\") {
        // On Windows: convert backslashes to web-friendly slashes.
        relativePath = relativePath.replace(/\\/g, "/");
      }
      const fullClassName = path.basename(file, ".js");
      // Strip 'Plain' from beginning of class name.
      const plainRegex = /^Plain(?<name>.+)/;
      const match = plainRegex.exec(fullClassName);
      const className = match ? match.groups.name : fullClassName;
      // Include a "." if there's not already a "." at the beginning.
      const importPath =
        relativePath[0] === "." ? relativePath : `./${relativePath}`;
      return `export { default as ${className} } from "${importPath}";`;
    })
    .join("\n");

  const helperFiles = sourceFiles.helpers.sort();
  const multipleExports = helperFiles
    .map((file) => {
      let relativePath = path.relative(destinationFolder, file);
      if (path.sep === "\\") {
        // On Windows: convert backslashes to web-friendly slashes.
        relativePath = relativePath.replace(/\\/g, "/");
      }
      const name = path.basename(file, ".js");
      // Include a "." if there's not already a "." at the beginning.
      const importPath =
        relativePath[0] === "." ? relativePath : `./${relativePath}`;
      return `import * as ${name}Import from "${importPath}";
// @ts-ignore
export const ${name} = ${name}Import;
`;
    })
    .join("\n");

  const content = `${header}

// Files that export a single object.
${defaultExports}

// Files that export multiple objects.
// As of Sept 2019, there's no way to simultaneously import a collection of
// objects and then export them as a named object, so we have to do the import
// and export in separate steps.
${multipleExports}`;

  await fs.writeFile(destination, content);
}

export default async function createLibraryFiles(sourceFiles, targetFolder) {
  // Write library files to /src folder,
  // and auto-define variations to /define folder.
  const libraryJsPath = path.join(targetFolder, "elix.js");
  const libraryTsPath = path.join(targetFolder, "elix.d.ts");
  await Promise.all([
    createLibraryFile(libraryJsPath, srcJsHeader, sourceFiles),
    createLibraryFile(libraryTsPath, tsHeader, sourceFiles),
  ]);
}

function sortByBaseName(files) {
  const result = files.slice();
  result.sort((a, b) => {
    const basenameA = path.basename(a);
    const basenameB = path.basename(b);
    if (basenameA < basenameB) {
      return -1;
    } else if (basenameA > basenameB) {
      return 1;
    } else {
      return 0;
    }
  });
  return result;
}
